package com.workingpub.commons.orm.internal;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorValue;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.workingpub.commons.domain.Identifiable;
import com.workingpub.commons.orm.JPAMappingBuilder;
import com.workingpub.commons.orm.SqlBuildEntry;

/**
 * JPA Annotation解析基类，封装对注解元信息解析，子类可直接使用
 * 
 * @author taoping
 *
 * @param <T>
 */
public abstract class AbstractSqlBuilder implements JPAMappingBuilder {
	protected final Logger logger = LoggerFactory.getLogger(getClass());

	protected Class<?> entityType;
	private List<EntityMetaInfo> entityMetaInfos;
	private List<Field> manyToManyFields;
	private List<PropertyDescriptor> manyToManyProperties;

	private Map<String, PropertyDescriptor> allProperties;

	private boolean isParse = false;

	private final ColumnParser[] Column_Parsers = new ColumnParser[] { new IdParser(), new ManyToOneParser(),
			new EmbeddedParser(), new OnlyColumnParser() };

	public <T extends Identifiable<?>> AbstractSqlBuilder(Class<T> type) {
		this.entityType = type;
	}

	@Override
	public <T extends Identifiable<?>> List<SqlBuildEntry> build(T entity) throws Exception {
		Assert.notNull(entity, "Persistence instance can't be null.");
		Assert.isTrue(entityType.isAnnotationPresent(Entity.class),
				"Persistence instance is not a JPA annotation object.");

		parseEntityMetaInfo();

		List<SqlBuildEntry> sqlBuildEntries = null;
		if (null != entityMetaInfos) {
			sqlBuildEntries = new ArrayList<SqlBuildEntry>();
			for (EntityMetaInfo entry : entityMetaInfos) {
				SqlBuildEntry sqlEntry;
				sqlEntry = build(entry, entity);
				if (null != sqlEntry) {
					sqlBuildEntries.add(sqlEntry);
				} else {
					logger.warn("Build result is null");
				}
			}
			// 多对多字段处理
			if (null != manyToManyFields && !manyToManyFields.isEmpty()) {
				for (Field entry : manyToManyFields) {
					List<SqlBuildEntry> entries = buildManyToMany(entry, entity);
					if (null != entries && !entries.isEmpty()) {
						sqlBuildEntries.addAll(entries);
					}
				}
			}
			// 多对多属性处理
			if (!CollectionUtils.isEmpty(manyToManyProperties)) {
				for (PropertyDescriptor entry : manyToManyProperties) {
					List<SqlBuildEntry> entries = buildManyToMany(entry, entity);
					if (null != entries && !entries.isEmpty()) {
						sqlBuildEntries.addAll(entries);
					}
				}
			}
		}
		return sqlBuildEntries;
	}

	/**
	 * 基类已判断实体是否为null和JPA注解对象<br>
	 * 基类已解析好实体JPA注解元信息,可直接操作属性：entityMetaInfos
	 * 
	 * @param metaInfo
	 * @param entity
	 * @return
	 */
	protected abstract <T extends Identifiable<?>> SqlBuildEntry build(EntityMetaInfo metaInfo, T entity)
			throws Exception;

	/**
	 * 子类根据SQL类型处理多对多情况下应生成的SQL
	 * 
	 * @param field
	 * @param entity
	 * @return
	 */
	protected abstract <T extends Identifiable<?>> List<SqlBuildEntry> buildManyToMany(Field field, T entity)
			throws Exception;

	/**
	 * 子类根据SQL类型处理多对多情况下应生成的SQL
	 * 
	 * @param property
	 * @param entity
	 * @return
	 */
	protected abstract <T extends Identifiable<?>> List<SqlBuildEntry> buildManyToMany(PropertyDescriptor property,
			T entity) throws Exception;

	private void parseEntityMetaInfo() {
		if (!isParse) {
			parseTable(entityType);
		}
	}

	private void parseTable(Class<?> type) {
		parseTable(type, null);
	}

	private void parseTable(Class<?> type, EntityMetaInfo metaInfo) {
		if (!type.isAnnotationPresent(Entity.class) && !type.isAnnotationPresent(MappedSuperclass.class)) {
			return;
		}

		if (null == entityMetaInfos) {
			entityMetaInfos = new ArrayList<EntityMetaInfo>();
		}
		if (null == metaInfo) {
			metaInfo = new EntityMetaInfo();
			entityMetaInfos.add(metaInfo);
		}
		if (type.isAnnotationPresent(Table.class)) {
			Table table = type.getAnnotation(Table.class);
			metaInfo.setTableName(table.name());
		}
		if (type.isAnnotationPresent(Cacheable.class)) {
			Cacheable cacheable = type.getAnnotation(Cacheable.class);
			metaInfo.setCache(cacheable.value());
		}
		if (type.isAnnotationPresent(DiscriminatorValue.class)) {
			DiscriminatorValue discriminatorValue = type.getAnnotation(DiscriminatorValue.class);
			metaInfo.setDiscriminatorValue(discriminatorValue.value());
		}
		if (type.isAnnotationPresent(PrimaryKeyJoinColumn.class)) {
			PrimaryKeyJoinColumn primaryKeyJoinColumn = type.getAnnotation(PrimaryKeyJoinColumn.class);
			metaInfo.setPrimaryKeyJoinColumn(primaryKeyJoinColumn.name());
		}
		if (type.isAnnotationPresent(DiscriminatorColumn.class)) {
			DiscriminatorColumn discriminatorColumn = type.getAnnotation(DiscriminatorColumn.class);
			metaInfo.setDiscriminatorColumn(discriminatorColumn.name());
			metaInfo.setDiscriminatorType(discriminatorColumn.discriminatorType());
		}

		// 基类解析
		if (type.getSuperclass().isAnnotationPresent(Inheritance.class)) {
			Inheritance inheritance = type.getSuperclass().getAnnotation(Inheritance.class);
			metaInfo.setInheritanceType(inheritance.strategy());
		}

		Assert.isTrue(!InheritanceType.TABLE_PER_CLASS.equals(metaInfo.getInheritanceType()),
				"Does't support this inheritance type : TABLE_PER_CLASS");

		// 单表继承
		if (InheritanceType.SINGLE_TABLE.equals(metaInfo.getInheritanceType())) {
			parseColumnMetaInfo(type, metaInfo);
			parseTable(type.getSuperclass(), metaInfo);
		}
		// 多表继承
		else if (InheritanceType.JOINED.equals(metaInfo.getInheritanceType())) {
			parseColumnMetaInfo(type, metaInfo);
			metaInfo.setIdname("id");// 用注解规则解析太麻烦，暂且用默认值

			EntityMetaInfo joinedMetaInfo = new EntityMetaInfo();
			entityMetaInfos.add(joinedMetaInfo);
			metaInfo.setParent(joinedMetaInfo);
			parseTable(type.getSuperclass(), joinedMetaInfo);
		} else {
			parseColumnMetaInfo(type, metaInfo);
			parseTable(type.getSuperclass(), metaInfo);
		}
	}

	void parseColumnMetaInfo(Class<?> type, EntityMetaInfo metaInfo) {
		parseFields(type, metaInfo);
		parseProperties(type, metaInfo);
	}

	private void parseFields(Class<?> type, EntityMetaInfo metaInfo) {
		Field[] fields = type.getDeclaredFields();
		if (null == fields || fields.length == 0) {
			return;
		}
		for (Field entry : fields) {
			// 多对多交由子类处理
			if (entry.isAnnotationPresent(ManyToMany.class) && entry.isAnnotationPresent(JoinTable.class)) {
				if (null == manyToManyFields) {
					manyToManyFields = new ArrayList<Field>();
				}
				manyToManyFields.add(entry);
			} else {
				for (ColumnParser parser : Column_Parsers) {
					if (parser.parseColumnAnnotations(entry, metaInfo)) {
						break;
					}
				}
			}
		}
	}

	private void parseProperties(Class<?> type, EntityMetaInfo metaInfo) {
		BeanInfo beanInfo = null;
		try {
			beanInfo = Introspector.getBeanInfo(type, type.getSuperclass());
		} catch (IntrospectionException e) {
			logger.error(e.getMessage(), e);
			Assert.isNull(e, e.getMessage());
		}
		PropertyDescriptor[] properties = beanInfo.getPropertyDescriptors();
		if (null == properties || properties.length == 0) {
			return;
		}

		if (null == allProperties) {
			allProperties = new HashMap<String, PropertyDescriptor>();
		}
		for (PropertyDescriptor entry : properties) {
			allProperties.put(entry.getName(), entry);
			// 多对多交由子类处理
			Method reader = entry.getReadMethod();
			if (reader.isAnnotationPresent(ManyToMany.class) && reader.isAnnotationPresent(JoinTable.class)) {
				if (null == manyToManyProperties) {
					manyToManyProperties = new ArrayList<PropertyDescriptor>();
				}
				manyToManyProperties.add(entry);
			} else {
				for (ColumnParser parser : Column_Parsers) {
					if (parser.parseColumnAnnotations(entry, metaInfo)) {
						break;
					}
				}
			}
		}
	}

	protected <T extends Identifiable<?>> Object convertValue(Object value, T entity) throws Exception {
		Object result = null;
		PropertyDescriptor property = null;
		if (value instanceof Field) {
			Field field = (Field) value;
			property = allProperties.get(field.getName());
		} else if (value instanceof PropertyDescriptor) {
			property = (PropertyDescriptor) value;
		}
		if (null != property) {
			result = property.getReadMethod().invoke(entity);
			if (result instanceof Identifiable<?>) {
				result = ((Identifiable<?>) result).getId();
			}
		}

		if (null != result && result.getClass().isEnum()) {
			return ((Enum<?>) result).ordinal();
		}

		return result;
	}

	protected <T extends Identifiable<?>> Collection<?> convertValues(Object value, T entity) throws Exception {
		Collection<?> result = null;
		PropertyDescriptor property = null;

		if (value instanceof Field) {
			Field field = (Field) value;
			property = allProperties.get(field.getName());
		} else if (value instanceof PropertyDescriptor) {
			property = (PropertyDescriptor) value;
		}
		if (null != property) {
			Object temp = property.getReadMethod().invoke(entity);
			if (temp instanceof Collection) {
				try {
					result = (Collection<?>) temp;
				} catch (Exception ex) {
					logger.warn("Convert result to Collection error : " + ex.getMessage(), ex);
				}
			}
		}

		return result;
	}

	class IdParser implements ColumnParser {

		@Override
		public boolean parseColumnAnnotations(Field field, EntityMetaInfo metaInfo) {
			if (field.isAnnotationPresent(Id.class)) {
				if (field.isAnnotationPresent(Column.class)) {
					Column column = field.getAnnotation(Column.class);
					metaInfo.setIdname(StringUtils.isEmpty(column.name()) ? field.getName() : column.name());
					metaInfo.addColumnInfo(metaInfo.getIdname(), field);
				} else {
					metaInfo.setIdname(field.getName());
					metaInfo.addColumnInfo(field.getName(), field);
				}
				return true;
			}
			return false;
		}

		@Override
		public boolean parseColumnAnnotations(PropertyDescriptor property, EntityMetaInfo metaInfo) {
			Method readMethod = property.getReadMethod();
			if (readMethod.isAnnotationPresent(Id.class)) {
				if (readMethod.isAnnotationPresent(Column.class)) {
					Column column = readMethod.getAnnotation(Column.class);
					metaInfo.setIdname(StringUtils.isEmpty(column.name()) ? property.getName() : column.name());
					metaInfo.addColumnInfo(metaInfo.getIdname(), property);
				} else {
					metaInfo.setIdname(property.getName());
					metaInfo.addColumnInfo(property.getName(), property);
				}
				return true;
			}
			return false;
		}

	}

	class OnlyColumnParser implements ColumnParser {
		@Override
		public boolean parseColumnAnnotations(Field field, EntityMetaInfo metaInfo) {
			if (field.isAnnotationPresent(Column.class)) {
				Column column = field.getAnnotation(Column.class);
				metaInfo.addColumnInfo(StringUtils.isEmpty(column.name()) ? field.getName() : column.name(), field);
				return true;
			}
			return false;
		}

		@Override
		public boolean parseColumnAnnotations(PropertyDescriptor property, EntityMetaInfo metaInfo) {
			Method readMethod = property.getReadMethod();
			if (readMethod.isAnnotationPresent(Column.class)) {
				Column column = readMethod.getAnnotation(Column.class);
				metaInfo.addColumnInfo(StringUtils.isEmpty(column.name()) ? property.getName() : column.name(),
						property);
				return true;
			}
			return false;
		}
	}

	class ManyToOneParser implements ColumnParser {

		@Override
		public boolean parseColumnAnnotations(Field field, EntityMetaInfo metaInfo) {
			if (field.isAnnotationPresent(ManyToOne.class)) {
				// ManyToOne manyToOne = field.getAnnotation(ManyToOne.class);
				if (field.isAnnotationPresent(JoinColumn.class)) {
					JoinColumn joinColumn = field.getAnnotation(JoinColumn.class);
					metaInfo.addColumnInfo(StringUtils.isEmpty(joinColumn.name()) ? field.getName() : joinColumn.name(),
							field);
				}
				return true;
			}
			return false;
		}

		@Override
		public boolean parseColumnAnnotations(PropertyDescriptor property, EntityMetaInfo metaInfo) {
			Method readMethod = property.getReadMethod();
			if (readMethod.isAnnotationPresent(ManyToOne.class)) {
				// ManyToOne manyToOne = field.getAnnotation(ManyToOne.class);
				if (readMethod.isAnnotationPresent(JoinColumn.class)) {
					JoinColumn joinColumn = readMethod.getAnnotation(JoinColumn.class);
					metaInfo.addColumnInfo(
							StringUtils.isEmpty(joinColumn.name()) ? property.getName() : joinColumn.name(), property);
				}
				return true;
			}
			return false;
		}

	}

	class EmbeddedParser implements ColumnParser {

		@Override
		public boolean parseColumnAnnotations(Field field, EntityMetaInfo metaInfo) {
			if (field.isAnnotationPresent(Embedded.class)) {
				parseColumnMetaInfo(field.getType(), metaInfo);
				return true;
			}
			return false;
		}

		@Override
		public boolean parseColumnAnnotations(PropertyDescriptor property, EntityMetaInfo metaInfo) {
			Method reader = property.getReadMethod();
			if (reader.isAnnotationPresent(Embedded.class)) {
				parseColumnMetaInfo(reader.getReturnType(), metaInfo);
				return true;
			}
			return false;
		}

	}
}
